package util;


import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import model.UserInfo;
import model.UserInfoClearText;

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
/**
 *
 * @author Ragnarokr
 */
public class CrackerHelper {

    /**
     * Checks a single word from a dictionary, against a list of encrypted passwords.
     * Tries different variations on the dictionary entry, like all uppercase, adding digits to the end of the entry, etc.
     *
     * @param dictionaryEntry a single word from a dictionary, i.e. a possible password
     * @param userInfos a list of user information records: username + encrypted password
     */
    public static List<UserInfoClearText> checkWordWithVariations(MessageDigest md, final String dictionaryEntry, final List<UserInfo> userInfos) {
        final String possiblePassword = dictionaryEntry;
        final List<UserInfoClearText> partialResult = checkSingleWord(md, userInfos, possiblePassword);
        final List<UserInfoClearText> results = new ArrayList<UserInfoClearText>();
        results.addAll(partialResult);

        results.addAll(checkForUpperCase(md, userInfos, dictionaryEntry));
        results.addAll(checkForCapitalized(md, userInfos, dictionaryEntry));
        results.addAll(checkForReverse(md, userInfos, dictionaryEntry));

        results.addAll(checkForEndDigit(md, userInfos, dictionaryEntry));
        results.addAll(checkForStartDigit(md, userInfos, dictionaryEntry));
        results.addAll(checkForStartEndDigit(md, userInfos, dictionaryEntry));

        return results;
    }

    /**
     * Checks if the word is spelled backwards
     * @param md
     * @param userInfos
     * @param dictionaryEntry
     * @return
     */
    private static List<UserInfoClearText> checkForReverse(MessageDigest md, List<UserInfo> userInfos, String dictionaryEntry) {
        final String possiblePasswordReverse = new StringBuilder(dictionaryEntry).reverse().toString();
        return checkSingleWord(md, userInfos, possiblePasswordReverse);
    }

    /**
     * Checks if the word is capitalized
     * @param md
     * @param userInfos
     * @param dictionaryEntry
     * @return
     */
    private static List<UserInfoClearText> checkForCapitalized(MessageDigest md, List<UserInfo> userInfos, String dictionaryEntry) {
        final String possiblePasswordCapitalized = StringUtilities.capitalize(dictionaryEntry);
        return checkSingleWord(md, userInfos, possiblePasswordCapitalized);
    }

    /**
     * Checks if the word is in full upper case
     * @param md
     * @param userInfos
     * @param dictionaryEntry
     * @return
     */
    private static List<UserInfoClearText> checkForUpperCase(MessageDigest md, List<UserInfo> userInfos, String dictionaryEntry) {
        final String possiblePasswordUpperCase = dictionaryEntry.toUpperCase();
        return checkSingleWord(md, userInfos, possiblePasswordUpperCase);
    }

    /**
     * Checks if there is a digit in front of the word and after the word
     * @param md
     * @param userInfos
     * @param dictionaryEntry
     * @return
     */
    private static List<UserInfoClearText> checkForStartEndDigit(MessageDigest md, List<UserInfo> userInfos, String dictionaryEntry) {
        final List<UserInfoClearText> results = new ArrayList<UserInfoClearText>();
        for (int i = 0; i < 10; i++) {
            for (int j = 0; j < 100; j++) {
                final String possiblePasswordStartEndDigit = i + dictionaryEntry + j;
                final List<UserInfoClearText> partialResultStartEndDigit = checkSingleWord(md, userInfos, possiblePasswordStartEndDigit);
                results.addAll(partialResultStartEndDigit);
            }
        }
        return results;
    }

    /**
     * Checks if there is a digit before the word
     * @param md
     * @param userInfos
     * @param dictionaryEntry
     * @return
     */
    private static List<UserInfoClearText> checkForStartDigit(MessageDigest md, List<UserInfo> userInfos, String dictionaryEntry) {
        final List<UserInfoClearText> results = new ArrayList<UserInfoClearText>();
        for (int i = 0; i < 100; i++) {
            final String possiblePasswordStartDigit = i + dictionaryEntry;
            final List<UserInfoClearText> partialResultStartDigit = checkSingleWord(md, userInfos, possiblePasswordStartDigit);
            results.addAll(partialResultStartDigit);
        }
        return results;
    }

    /**
     * Checks if there is a digit in the end of the word
     * @param md
     * @param userInfos
     * @param dictionaryEntry
     * @return
     */
    private static List<UserInfoClearText> checkForEndDigit(MessageDigest md, List<UserInfo> userInfos, String dictionaryEntry) {
        final List<UserInfoClearText> results = new ArrayList<UserInfoClearText>();
        for (int i = 0; i < 100; i++) {
            final String possiblePasswordEndDigit = dictionaryEntry + i;
            final List<UserInfoClearText> partialResultEndDigit = checkSingleWord(md, userInfos, possiblePasswordEndDigit);
            results.addAll(partialResultEndDigit);
        }
        return results;
    }

    /**
     * Check a single  word (may include a single variation)from the dictionary against a list of encrypted passwords
     * @param userInfos a list of user information records: username + encrypted password
     * @param possiblePassword a single dictionary entry (may include a single variation)
     * @return the user information record, if the dictionary entry matches the users password, or {@code  null} if not.
     */
    private static List<UserInfoClearText> checkSingleWord(MessageDigest md, final List<UserInfo> userInfos, final String possiblePassword) {
        final byte[] digest = md.digest(possiblePassword.getBytes());
        final List<UserInfoClearText> results = new ArrayList<UserInfoClearText>();
        for (UserInfo userInfo : userInfos) {
            if (Arrays.equals(userInfo.getEntryptedPassword(), digest)) {
                results.add(new UserInfoClearText(userInfo.getUsername(), possiblePassword));
            }
        }
        return results;
    }
}
